Учебный курс: Подготовка на 1С:Специалист по платформе 1С:Предприятие 8.3

Общие приемы и механизмы решения задач – тема № 13:
«Подводный камень» проблемы копеек: определение момента последнего списания

Вернемся к тестовому примеру из предыдущей темы 12. Что такое «проблема копеек» и как ее решить.

В документе «Расходная накладная № 1» внесем некоторые изменения – создадим строки с одинаковой номенклатурой:

Документ «Расходная накладная № 1»

Рисунок 1 – Документ «Расходная накладная № 1»

Проводим документ и в движениях по регистру видим нерешенную «проблему копеек»:

Движения по РН «Остатки номенклатуры»

Рисунок 2 – Движения по РН «Остатки номенклатуры»

При этом результат будет совершенно одинаков для каждой формулы из списка. Чтобы убедиться, достаточно посмотреть в диагностические сообщения при проведении «Расходной накладной».

Давайте разбираться, в чем дело.

Результат запроса получения данных для расчета себестоимости

Рисунок 3 – Результат запроса получения данных для расчета себестоимости

Из рисунка видим, что при обходе выборки из результата запроса условие КоличествоОстаток=КоличествоСписания для каждой строки всегда равно ложь, поэтому всегда выполняется расчет по формуле 100 * 1 / 3 = 33,33 руб.

Вспомним, что при подготовке запроса был преднамеренно закомментирован код для группировки по номенклатуре. Вот это и есть последствия: если забыть сделать группировку товаров, то «проблема копеек» останется нерешенной.

В процессе решения задачи группировка данных табличной части документа может быть не только «забытой». Она может быть нецелесообразной из-за каких-либо условий задачи или, например, можно получать одним и тем же запросом данные не только для формирования движений по списанию себестоимости, но и по другому регистру.

Так, в реальной практике может быть необходимо записывать в движения по списанию себестоимости еще какую-либо дополнительную информацию из табличной части в реквизиты регистра, например, цену продажи или склад-отправитель. В задачах из сборника таких требований нет, но в жизни такие требования вполне могут быть.

Разберем, что в такой ситуации нужно изменить в алгоритме, чтобы все-таки решить «проблему копеек».

Все просто. В результирующем запросе будем получать итоги по полям, в разрезе которых получаем данные из таблицы остатков. В нашей задаче это – Номенклатура:

Движения по РН «Остатки номенклатуры»

Рисунок 4 – Движения по РН «Остатки номенклатуры»

Из схемы видим, что можно определить, является ли списание последним, если сравнить списанное количество по детальным записям со значением КоличествоОстаток из строки итогов.

Для обхода выборки из результата запроса нам понадобятся два цикла: один для обхода по иерархии, а второй по детальным записям.

Новый листинг обработки проведения:

Процедура ОбработкаПроведения(Отказ, РежимПроведения)
	// 1. Подготовка наборов записей регистра
	Движения.ОстаткиНоменклатуры.Записывать = Истина;
	Движения.Записать();
	// 2. Установка маркера Записи у регистра
	Движения.ОстаткиНоменклатуры.Записывать = Истина;
	
	// 3. Запрос для получения данных для расчета себестоимости
	Запрос = новый Запрос("ВЫБРАТЬ
	|	РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура,
	|	РасходнаяНакладнаяСписокНоменклатуры.Количество КАК Количество
	|ПОМЕСТИТЬ ТЧСписокНоменклатуры
	|ИЗ
	|	Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
	|ГДЕ
	|	РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
	|
	|ИНДЕКСИРОВАТЬ ПО
	|	Номенклатура
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	ТЧСписокНоменклатуры.Номенклатура КАК Номенклатура,
	|	ПРЕДСТАВЛЕНИЕ(ТЧСписокНоменклатуры.Номенклатура) КАК НоменклатураПредставление,
	|	ТЧСписокНоменклатуры.Количество КАК Количество,
	|	ЕСТЬNULL(ОстаткиНоменклатурыОстатки.СуммаОстаток, 0) КАК СуммаОстаток,
	|	ЕСТЬNULL(ОстаткиНоменклатурыОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
	|ИЗ
	|	ТЧСписокНоменклатуры КАК ТЧСписокНоменклатуры
	|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки(
	|				&МоментВремени,
	|				Номенклатура В
	|					(ВЫБРАТЬ
	|						ТЧСписокНоменклатуры.Номенклатура
	|					ИЗ
	|						ТЧСписокНоменклатуры КАК ТЧСписокНоменклатуры)) КАК ОстаткиНоменклатурыОстатки
	|		ПО ТЧСписокНоменклатуры.Номенклатура = ОстаткиНоменклатурыОстатки.Номенклатура
	|ИТОГИ
	|	СУММА(Количество),
	|	МАКСИМУМ(СуммаОстаток),
	|	МАКСИМУМ(КоличествоОстаток)
	|ПО
	|	Номенклатура");
	
	//4. Заполнение параметров запроса
	Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
	Запрос.УстановитьПараметр("Ссылка", Ссылка);
	
	//5.выполнение запроса и организация цикла по итоговым записям 
	Результат = Запрос.Выполнить();
	ВыборкаНоменклатура = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
	
	Пока ВыборкаНоменклатура.Следующий() Цикл

		// 6. Контроль остатков
		Если ВыборкаНоменклатура.Количество>ВыборкаНоменклатура.КоличествоОстаток Тогда
			
			Нехватает = ВыборкаНоменклатура.Количество-ВыборкаНоменклатура.КоличествоОстаток;
			Сообщение = Новый СообщениеПользователю;
			Сообщение.Текст = "Не хватает остатка по номенклатуре " + ВыборкаНоменклатура.НоменклатураПредставление 
			+ " в количестве: " +  Нехватает;
			Сообщение.Сообщить();
			Отказ = Истина;				
		КонецЕсли;

		//7. Остатка не хватает, нет смысла формировать движения
		Если Отказ Тогда
			Продолжить;
		КонецЕсли;
		
		//8. зафиксируем значения вспомогательных переменных
		КоличествоОстаток = ВыборкаНоменклатура.КоличествоОстаток;
		СуммаОстаток = ВыборкаНоменклатура.СуммаОстаток;
		СписаноКоличество = 0; 
		СписаноСумма = 0;
		
		//9. организуем цикл по детальным записям
		ВыборкаДетальныеЗаписи = ВыборкаНоменклатура.Выбрать();
		
		Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
			
			Движение = Движения.ОстаткиНоменклатуры.ДобавитьРасход();
			Движение.Период = Дата;
			Движение.Номенклатура = ВыборкаНоменклатура.Номенклатура;
			
			//9.1 записываем в движение количество списания
			КоличествоСписания = ВыборкаДетальныеЗаписи.Количество;
			Движение.Количество = КоличествоСписания;
			//9.2 увеличиваем счетчик списанного количества 
			СписаноКоличество = СписаноКоличество+КоличествоСписания;
			
			//9.3 расчет себестоимости списания
			Себестоимость=?(СписаноКоличество = КоличествоОстаток,
							СуммаОстаток - СписаноСумма,
							СуммаОстаток*КоличествоСписания/КоличествоОстаток);
			
			Движение.Сумма = Себестоимость;	
			
			//9.4 увеличиваем счетчик списанной суммы именно по данным движений! 
			//Это важно, чтобы учесть ошибку округления при последнем списании.
			СписаноСумма = СписаноСумма + Движение.Сумма;
		КонецЦикла;
		
	КонецЦикла;
	
КонецПроцедуры

Разберем отличия алгоритма

Запрос получения данных (п. 3)

Первый запрос остался без изменений. Во втором запросе добавлены итоги по номенклатуре. В итогах подсчитаем общее количество списания по номенклатуре СУММА(Количество). По полям из таблицы остатков регистра КоличествоОстаток и СтоимостьОстаток для итогов берем максимум. Их суммировать не нужно, т.к. это и есть остатки по номенклатуре.

Обход выборки из результата запроса (пп. 5 – 9.4)

Получаем результат запроса и обходим выборку в цикле.

У нас будет два цикла:

  • Внешний цикл – обход выборки по номенклатуре
  • Вложенный цикл – обход детальных записей.

В рамках итерации внешнего цикла (обхода по номенклатуре) выполняем контроль остатков, а также заводим переменные СписаноКоличество и СписаноСумма. Устанавливаем их значение в 0.

В цикле по детальным записям создаем движения для записи в регистр.

Списываемое количество берем из детальных записей. Не забываем увеличить счетчик списанного количества – значение переменной СписаноКоличество.

Списание будет являться последним, если СписаноКоличество будет равно КоличествоОстаток. При последнем списании не будем рассчитывать сумму списания по формуле, а возьмем значение СуммыОстаток из строки итогов за вычетом суммы, которую мы уже успели списать и накопили в переменной СписаноСумма.

Записываем найденную сумму списания в ресурс регистра.

Не забываем увеличить значение переменной СписаноСумма.

Важно: для увеличения переменной СписаноСумма прибавляем именно значение Движение.Сумма, а не значение переменной Себестоимость. Нужно помнить, что при записи в ресурс регистра происходит автоматическое округление, а в переменной СписаноСумма нужно накапливать именно суммы в том виде, как они были записаны в регистр. Только в этом случае при последнем списании мы сможем получить с помощью формулы СуммаОстаток – СписаноСумма значение с учетом ошибки округления и, таким образом, вывести суммовой остаток по регистру в ноль.

Проверим результат работы нашего алгоритма. Для этого перепроведем документ «Расходная накладная № 1»:

Движения по РН «Остатки номенклатуры»

Рисунок 5 – Движения по РН «Остатки номенклатуры»

Видим, что теперь все в порядке. «Проблема копеек» решена.

Подведем итоги

Мы завершили разбор «проблемы копеек» и теперь знаем, что для ее решения и на экзамене, и в реальной практике нужно:

  • Использовать формулу с учетом последнего списания
  • Правильно определять условие, при котором списание действительно будет являться последним.

Также на практике убедились, что при проверке решения задачи совсем не лишними бывают тестовые примеры с дублями строк в табличной части документа.

Перейти к следующей теме:
“Что нужно знать о партионном учете и методах учета себестоимости для успешной сдачи экзамена” (№ 14)

Комментарии закрыты